﻿using System;
using System.Linq;
using System.Linq.Expressions;

using VirtualTournaments.DataAccess.SqlServer.DataProxies;
using VirtualTournaments.DataAccess.SqlServer.Exceptions;

namespace VirtualTournaments.DataAccess.SqlServer.DataMappers
{
	internal abstract class AbstractDataMapper<TDataObject, TBusinessObject, TDataProxy>
		where TDataProxy : TBusinessObject, Identifiable<TDataObject>, IDataProxy<TDataObject>
		where TDataObject : class, IDataObject, new()
	{
		internal TBusinessObject[] GetList()
		{
			return GetList(null);
		}

		internal TBusinessObject[] GetList(Expression<Func<TDataObject, bool>> whereExpression)
		{
			IQueryable<TDataObject> dataTable = GetDataTable().AsQueryable();
			if (whereExpression != null)
				dataTable = dataTable.Where(whereExpression);
			return dataTable.Select(dataObject => DataObject2BusinessObject(dataObject)).ToArray();
		}

		internal TBusinessObject GetOneAsBusinessObject(Expression<Func<TDataObject, bool>> whereExpression)
		{
			return GetOne<TBusinessObject>(whereExpression, dataObject => DataObject2BusinessObject(dataObject));
		}

		internal TDataObject GetOneAsDataObject(Expression<Func<TDataObject, bool>> whereExpression)
		{
			return GetOne<TDataObject>(whereExpression, dataObject => dataObject);
		}

		private T GetOne<T>(Expression<Func<TDataObject, bool>> whereExpression, Expression<Func<TDataObject, T>> selectExpression)
		{
			IQueryable<TDataObject> dataTable = GetDataTable().AsQueryable();
			dataTable = dataTable.Where(whereExpression);
			return dataTable.Select(selectExpression).FirstOrDefault();
		}

		internal abstract VirtualTournamentsDataContext GetDataContext();
		internal abstract System.Data.Linq.Table<TDataObject> GetDataTable();
		internal abstract TBusinessObject DataObject2BusinessObject(TDataObject dataObject);
		internal abstract void BusinessObject2DataObject(TBusinessObject businessObject, TDataObject dataObject);
		internal abstract void DataProxy2DataObject(TDataProxy dataProxy, TDataObject dataObject);

		internal TDataObject BusinessObject2DataObject(TBusinessObject businessObject)
		{
			TDataObject dataObject = new TDataObject();
			if (businessObject is TDataProxy)
			{
				DataProxy2DataObject((TDataProxy)businessObject, dataObject);
			}
			else
			{
				BusinessObject2DataObject(businessObject, dataObject);
			}
			return dataObject;
		}

		internal TDataObject Save(TBusinessObject entity)
		{
			if (entity is TDataProxy)
			{
				TDataProxy dataProxy = (TDataProxy)entity;
				Update(dataProxy);
				return dataProxy.InnerDataObject;
			}
			else
			{
				return Create(entity);
			}
		}

		internal TDataObject Create(TBusinessObject entity)
		{
			TDataObject dataObject = BusinessObject2DataObject(entity);
			GetDataTable().InsertOnSubmit(dataObject);
			//GetDataContext().SubmitChanges();
			return dataObject;
		}

		internal void Update(TDataProxy dataProxy)
		{
			TDataObject dataObject = GetOneAsDataObject(((Identifiable<TDataObject>)dataProxy).GetUniqueWhereExpression());
			if (dataObject == null)
			{
				throw new EntityWasDeletedSinceLastRead();
			}
			else if (((IDataObject)dataObject).Timestamp != dataProxy.InnerDataObject.Timestamp)
			{
				throw new EntityWasModifiedSinceLastRead();
			}
			DataProxy2DataObject(dataProxy, dataProxy.InnerDataObject);
			//GetDataContext().SubmitChanges();
		}

		internal void Delete(TBusinessObject entity)
		{
			if (entity is TDataProxy)
			{
				Delete(((TDataProxy)entity).InnerDataObject);
			}
			else
			{
				throw new EntityMustBeLoadedBeforeDeleting();
			}
		}

		internal void Delete(TDataObject entity)
		{
			GetDataTable().DeleteOnSubmit(entity);
			//GetDataContext().SubmitChanges();
		}
	}
}
